package com.dt.common.validation.impl;

import java.beans.PropertyDescriptor;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Field;
import java.nio.charset.Charset;
import java.util.regex.Pattern;

import org.springframework.beans.BeanUtils;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.dt.common.validation.ValidatorConfiguration;
import com.dt.common.validation.ValidatorIdentity;
import com.dt.common.validation.exception.BuildingException;
import com.dt.common.validation.itf.ConstraintValidation;
import com.dt.common.validation.itf.DefinitionReader;
import com.dt.common.validation.itf.PropertyGetter;
import com.dt.common.validation.parser.DefinitionParser;
import com.dt.common.validation.parser.DefinitionParsers;

public class JsonDefinitionReader implements DefinitionReader {

	@Override
	public ObjectValidationDefinition read(ValidatorIdentity id,
			ValidatorConfiguration configuration) {
		ObjectValidationDefinition objectDefinition = new ObjectValidationDefinition(
				id);

		try {
			String content = readJsonContent(configuration);
			JSONObject contentObj = JSON.parseObject(content);

			String metaClass = contentObj.getString("metaClass");
			if (metaClass == null
					|| !configuration.getMetaClass().getName()
							.equals(metaClass)) {
				throw new BuildingException(
						"meta class in resource file not equals with the class in configuration.");
			}

			JSONArray validations = contentObj.getJSONArray("validations");
			if (validations != null) {
				for (int i = 0; i < validations.size(); ++i) {
					JSONObject validation = validations.getJSONObject(i);

					parseEachValidation(configuration, objectDefinition,
							validation);
				}
			}

		} catch (IOException e) {
			throw new BuildingException(
					"exception when reading definition resource of "
							+ configuration.getOverridedDefinitionResource(), e);
		}
		return objectDefinition;
	}

	private void parseEachValidation(ValidatorConfiguration configuration,
			ObjectValidationDefinition objectDefinition, JSONObject validation) {
		final String type = validation.getString("type");
		final String property = validation.getString("property");
		Boolean abandoned_ = validation.getBoolean("abandoned");
		final boolean abandoned = abandoned_ == null ? false : abandoned_
				.booleanValue();

		if (property == null || property.isEmpty()) {
			throw new BuildingException("property can not be empty");
		}
		if (type == null || type.isEmpty()) {
			throw new BuildingException("type can not be empty");
		}

		try {
			Field field = configuration.getMetaClass().getDeclaredField(
					property);
			if (field.isSynthetic()) {
				throw new BuildingException("field " + property
						+ " can not be synthetic");
			}
			DefinitionParser parser = DefinitionParsers.getParser(type);
			if (parser == null) {
				throw new BuildingException("parser of type " + type
						+ " not registered");
			}

			DefaultValidationDefinition definition;
			if (abandoned) {
				definition = new DefaultValidationDefinition() {
					@Override
					public ConstraintValidation buildValidation() {
						return null;
					}
				};
				definition.setName(type);

			} else {
				PropertyDescriptor propertyDescriptor = BeanUtils
						.getPropertyDescriptor(configuration.getMetaClass(),
								field.getName());
				PropertyGetter propertyGetter = new DefaultPropertyGetter(
						propertyDescriptor, configuration.isSupportMapObject());

				definition = parser.parseFromJson(validation
						.getJSONObject("metaValues"));
				definition.setPropertyGetter(propertyGetter);
			}

			if (definition != null) {
				definition.setPropertyName(property);
				definition.setAbandoned(abandoned);

				objectDefinition.addPropertyDefinition(definition);
			}

		} catch (NoSuchFieldException | SecurityException e) {
			throw new BuildingException("field " + property
					+ " not found in class "
					+ configuration.getMetaClass().getName());
		}
	}

	private String resolveName(String name) {
		if (name == null || name.isEmpty()) {
			throw new IllegalArgumentException(
					"resource path can't be empty value");
		}
		if (name.startsWith("/")) {
			name = name.substring(1);
		} else {
			throw new IllegalArgumentException(
					"resource path must be start with '/', but was " + name);
		}
		return name;
	}

	private InputStream loadResourceAsStream(String path) {
		InputStream is = Thread.currentThread().getContextClassLoader()
				.getResourceAsStream(path);

		if (is == null) {
			is = getClass().getClassLoader().getResourceAsStream(path);
		}

		if (is == null) {
			is = ClassLoader.getSystemResourceAsStream(path);
		}
		return is;
	}

	private String readJsonContent(ValidatorConfiguration configuration)
			throws IOException {
		StringBuilder sb = new StringBuilder();

		Pattern commentPattern = Pattern.compile("^[\t ]*//.*$");

		String resourcePath = configuration.getOverridedDefinitionResource();
		if (resourcePath != null && !resourcePath.isEmpty()) {
			try (InputStream is = loadResourceAsStream(resolveName(resourcePath));
					BufferedReader reader = new BufferedReader(
							new InputStreamReader(is,
									Charset.forName(configuration
											.getResourceCharset())))) {

				String line = null;
				while (null != (line = reader.readLine())) {
					if (commentPattern.matcher(line).matches()) {
						continue;
					}
					sb.append(line);
					sb.append("\r\n");
				}
			}
		}
		return sb.toString();
	}
}
